####梯度法？什么是梯度，我们刚才学习了梯度，我们知道它的作用是，它所指向的方向即为函数值减小最多的方向（虽然暂时还不知道为什么，但是我们先记住，说不定后面就知道了）
####那好，我们知道了梯度的作用，那我们再回到我们这一张的主题，神经网络的学习，学习的目的就是为了找到最优的权重参数和偏置，而最优的指标是什么？是损失函数值的大小，
####损失函数值越小，说明我们的权重越优，那么这样一来，梯度就用上了，我们用梯度来寻找损失函数值最小。这个过程称之为梯度法
####总结一下，使用梯度来寻求函数最小值，即为梯度法（这里我们主要是使用梯度下降法（因为梯度法还包含梯度上升法））

#求解梯度的函数
#注意这里传入的x，是一组参数，例如上例中的（3，4），其中一个代表x0，一个代表x1
import numpy as np


def numerical_gradient(f,x):
    h = 1e-4 #老规矩，定义极小值
    grad = np.zeros_like(x) #生成和x一样形状的数组，不过是里面的元素都是0，它将用来存放我们求解完成的梯度
    #下面开始进行计算,中心差分法，我们这里计算的逻辑其实依旧还是没有变，每次遍历我们都只是求某 一个 参数对应的偏导数
    for idx in range(x.size):
        tmp_val = x[idx] #这个值先存下来，后面有用
        #f(x + h)的计算
        x[idx] = tmp_val + h #中心前，自变量的值
        fxh1 = f(x) #注意看这里，我是把整套参数都扔进去计算了，但是此前，我只改变过其中的一个参数的值，这符合我们求偏导数的思想
        #f(x - h)的计算
        x[idx] = tmp_val - h #中心后，自变量的值（可怜的x[idx],在整个过程中一直被拿来做容器）
        fxh2 = f(x) #自己想
        grad[idx] = (fxh1 - fxh2)/(2*h) #每次遍历完成一个偏导数的求解，放入盛放梯度的容器中
        x[idx] = tmp_val #还原值（看到没，这个值用上了，不过这个值分明很忙一直都在用嘛）
    return grad #循环完成之时，每个参数对应的偏导数自然也就求解放好了，那么自然完成了梯度的求解

#梯度法调参
def gradient_descent(f,init_x,lr=0.01,step_num=100):
    x = init_x.copy() #复制一份参数数据出来，不要引用
    for i in range(step_num):
        grad = numerical_gradient(f,x) #获得梯度
        x -= lr * grad #根据梯度法公式调整参数(新参数等于老参数减去学习率乘于偏导数)
    return x

#将优化的函数
def function_1(x):
    return x[0]**2 + x[1]**2

if __name__ == '__main__':
    ##问题，使用梯度法求 f(x0 + x1) = x0方 + x1方  的最小值
    init_x = np.array([-3.0,4.0]) #初始参数
    mini_x = gradient_descent(function_1,init_x,lr=0.1,step_num=100) #[-6.11110793e-10  8.14814391e-10]，使用梯度法开始优化参数
    ##把学习率设置得大一些试试
    mini_x_1 = gradient_descent(function_1,init_x,lr=10,step_num=100) #[-2.58983747e+13 -1.29524862e+12]，学习率过大，发散成了很大的值？（哪里大了？）
    ##把学习率设置得小一些试试
    mini_x_2 = gradient_descent(function_1,init_x,lr=1e-10,step_num=100) #[-2.99999994  3.99999992]，学习率过小，基本没怎么更新就结束了，这个看出来了
    print(mini_x)
    print(mini_x_1)
    print(mini_x_2)
    ##测试一些东西，看看这个参数求对应的函数值是不是最小的
    print(function_1(np.array([-6.11110793e-10,8.14814391e-10])))  #1.0373788931017896e-18，的确，这个函数值是够小了
    print(function_1(np.array([-2.58983747e+13,-1.29524862e+12]))) #6.72403481089212e+26
    print(function_1(np.array([-2.99999994,3.99999992]))) #24.99999900000001
